package com.hxb.example.handler;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.hxb.example.api.XxlJobTriggerClient;
import com.hxb.example.entity.XxlJobGroup;
import com.hxb.example.entity.XxlJobInfo;
import com.hxb.example.entity.XxlJobLog;
import com.hxb.example.enums.ExecutorBlockStrategyEnum;
import com.hxb.example.enums.TriggerTypeEnum;
import com.hxb.example.model.ReturnT;
import com.hxb.example.model.TriggerParam;
import com.hxb.example.properties.XxlJobProperties;
import com.hxb.example.route.ExecutorRouteStrategyEnum;
import com.hxb.example.service.XxlJobGroupService;
import com.hxb.example.service.XxlJobInfoService;
import com.hxb.example.service.XxlJobLogService;
import com.hxb.example.util.I18nUtil;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.openfeign.FeignClientBuilder;
import org.springframework.cloud.openfeign.FeignClientFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * xxl-job trigger
 * Created by xuxueli on 17/7/13.
 */
@Slf4j
@Component
public class XxlJobTrigger {

    @Resource
    private XxlJobInfoService xxlJobInfoService;
    @Resource
    private XxlJobGroupService xxlJobGroupService;
    @Resource
    private XxlJobLogService xxlJobLogService;
    @Resource
    private ApplicationContext applicationContext;
    @Resource
    private XxlJobProperties xxlJobProperties;
    @Resource
    private DiscoveryClient discoveryClient;
    private FeignClientBuilder feignClientBuilder;

    @PostConstruct
    public void init() {
        feignClientBuilder = new FeignClientBuilder(applicationContext);
    }

    /**
     * trigger job
     *
     * @param jobId
     * @param triggerType
     * @param failRetryCount        >=0: use this param
     *                              <0: use param from job info config
     * @param executorShardingParam
     * @param executorParam         null: use job param
     *                              not null: cover job param
     * @param addressList           null: use executor addressList
     *                              not null: cover
     */
    public void trigger(int jobId,
                        TriggerTypeEnum triggerType,
                        int failRetryCount,
                        String executorShardingParam,
                        String executorParam,
                        String addressList) {
        // load data
        XxlJobInfo jobInfo = xxlJobInfoService.getById(jobId);
        if (jobInfo == null) {
            log.warn(">>>>>>>>>>>> trigger fail, jobId invalid，jobId={}", jobId);
            return;
        }
        if (executorParam != null) {
            jobInfo.setExecutorParam(executorParam);
        }
        int finalFailRetryCount = failRetryCount >= 0 ? failRetryCount : jobInfo.getExecutorFailRetryCount();

        XxlJobGroup group = xxlJobGroupService.getById(jobInfo.getJobGroup());

        // cover addressList
        List<String> registryList;
        if (StringUtils.hasLength(addressList)) {
            // group.setAddressType(1);
            // group.setAddressList(addressList.trim());
            registryList = Arrays.asList(addressList.split(","));
        } else {
            registryList = getRegistryList(group.getAppname());
        }

        // sharding param
        int[] shardingParam = null;
        if (executorShardingParam != null) {
            String[] shardingArr = executorShardingParam.split("/");
            if (shardingArr.length == 2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) {
                shardingParam = new int[2];
                shardingParam[0] = Integer.parseInt(shardingArr[0]);
                shardingParam[1] = Integer.parseInt(shardingArr[1]);
            }
        }
        if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null)
                && shardingParam == null && CollectionUtil.isNotEmpty(registryList)) {
            for (int i = 0; i < registryList.size(); i++) {
                processTrigger(jobInfo, registryList, finalFailRetryCount, triggerType, i);
            }
        } else {
            processTrigger(jobInfo, registryList, finalFailRetryCount, triggerType, 0);
        }

    }

    private boolean isNumeric(String str) {
        try {
            Integer.valueOf(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    private void processTrigger(XxlJobInfo jobInfo, List<String> registryList, int finalFailRetryCount, TriggerTypeEnum triggerType, int index) {

        // param
        ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), ExecutorBlockStrategyEnum.SERIAL_EXECUTION);  // block strategy
        ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null);    // route strategy
        String shardingParam = (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) ? String.valueOf(index).concat("/").concat(String.valueOf(registryList.size())) : null;

        // 1、save log-id
        XxlJobLog jobLog = new XxlJobLog();
        jobLog.setJobGroup(jobInfo.getJobGroup());
        jobLog.setJobId(jobInfo.getId());
        jobLog.setTriggerTime(LocalDateTime.now());
        xxlJobLogService.save(jobLog);
        log.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId());

        // 2、init trigger-param
        TriggerParam triggerParam = new TriggerParam();
        triggerParam.setJobId(jobInfo.getId());
        triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
        triggerParam.setExecutorParams(jobInfo.getExecutorParam());
        triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
        triggerParam.setExecutorTimeout(jobInfo.getExecutorTimeout());
        triggerParam.setLogId(jobLog.getId());
        triggerParam.setLogDateTime(LocalDateTimeUtil.toEpochMilli(jobLog.getTriggerTime()));
        triggerParam.setGlueType(jobInfo.getGlueType());
        triggerParam.setGlueSource(jobInfo.getGlueSource());
        triggerParam.setGlueUpdatetime(LocalDateTimeUtil.toEpochMilli(jobInfo.getGlueUpdatetime()));
        triggerParam.setBroadcastIndex(index);
        triggerParam.setBroadcastTotal(registryList.size());

        // 3、init address
        String address = null;
        ReturnT<String> routeAddressResult = null;
        if (CollectionUtil.isNotEmpty(registryList)) {
            if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) {
                if (index < registryList.size()) {
                    address = registryList.get(index);
                } else {
                    address = registryList.get(0);
                }
            } else {
                routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, registryList);
                if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {
                    address = routeAddressResult.getContent();
                }
            }
        } else {
            routeAddressResult = new ReturnT<>(ReturnT.FAIL_CODE, I18nUtil.getString("jobconf_trigger_address_empty"));
        }

        // 4、trigger remote executor
        ReturnT<String> triggerResult = null;
        if (address != null) {
            triggerResult = runExecutor(triggerParam, address);
        } else {
            triggerResult = new ReturnT<String>(ReturnT.FAIL_CODE, null);
        }

        // 5、collection trigger info
        StringBuffer triggerMsgSb = new StringBuffer();
        triggerMsgSb.append(I18nUtil.getString("jobconf_trigger_type")).append("：").append(triggerType.getTitle());
        // triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append("：").append(IpUtil.getIp());
        // triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regtype")).append("：")
        //         .append((group.getAddressType() == 0) ? I18nUtil.getString("jobgroup_field_addressType_0") : I18nUtil.getString("jobgroup_field_addressType_1"));
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regaddress")).append("：").append(registryList);
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorRouteStrategy")).append("：").append(executorRouteStrategyEnum.getTitle());
        if (shardingParam != null) {
            triggerMsgSb.append("(" + shardingParam + ")");
        }
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorBlockStrategy")).append("：").append(blockStrategy.getTitle());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_timeout")).append("：").append(jobInfo.getExecutorTimeout());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append("：").append(finalFailRetryCount);

        triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>" + I18nUtil.getString("jobconf_trigger_run") + "<<<<<<<<<<< </span><br>")
                .append((routeAddressResult != null && routeAddressResult.getMsg() != null) ? routeAddressResult.getMsg() + "<br><br>" : "").append(triggerResult.getMsg() != null ? triggerResult.getMsg() : "");

        // 6、save log trigger-info
        jobLog.setExecutorAddress(address);
        jobLog.setExecutorHandler(jobInfo.getExecutorHandler());
        jobLog.setExecutorParam(jobInfo.getExecutorParam());
        jobLog.setExecutorShardingParam(shardingParam);
        jobLog.setExecutorFailRetryCount(finalFailRetryCount);
        // jobLog.setTriggerTime();
        jobLog.setTriggerCode(triggerResult.getCode());
        jobLog.setTriggerMsg(triggerMsgSb.toString());
        xxlJobLogService.updateById(jobLog);
        log.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
    }

    /**
     * run executor
     *
     * @param triggerParam
     * @param address
     * @return
     */
    public ReturnT<String> runExecutor(TriggerParam triggerParam, String address) {
        // ReturnT<String> runResult = null;
        // try {
        //     ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
        //     runResult = executorBiz.run(triggerParam);
        // } catch (Exception e) {
        //     log.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
        //     runResult = new ReturnT<String>(ReturnT.FAIL_CODE, ThrowableUtil.toString(e));
        // }
        FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
        factoryBean.setFallbackFactory(XxlJobTriggerClient.XxlJobTriggerClientFallback.class);
        factoryBean.setApplicationContext(applicationContext);
        factoryBean.setUrl(address);
        factoryBean.setType(XxlJobTriggerClient.class);
        XxlJobTriggerClient triggerClient = (XxlJobTriggerClient) factoryBean.getObject();
        ReturnT<String> runResult = triggerClient.triggerJob(xxlJobProperties.getAccessToken(), triggerParam);

        StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + "：");
        runResultSB.append("<br>address：").append(address);
        runResultSB.append("<br>code：").append(runResult.getCode());
        runResultSB.append("<br>msg：").append(runResult.getMsg());

        runResult.setMsg(runResultSB.toString());
        return runResult;
    }

    private List<String> getRegistryList(String appName) {
        List<ServiceInstance> instances = discoveryClient.getInstances(appName);
        for (ServiceInstance instance : instances) {
            System.out.println(instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort());
        }
        return instances.stream().map(instance -> instance.getUri().toString()).collect(Collectors.toList());
    }

}
